use crate::{
    cfg::{ROOT_ACCOUNT, ROOT_NAME, ROOT_PWD},
    error::{new_err_static, AppErr, SqlxErr},
    utils::pwd_encrypt,
};
use serde::Serialize;
use sqlx::Row;
use sqlx::{Executor, SqliteConnection};

use super::get_pool;

const CREATE_SQL: &'static str = r#"
    CREATE TABLE IF NOT EXISTS tb_user (
        id INTEGER PRIMARY KEY AUTOINCREMENT, 
        name TEXT NOT NULL, 
        account TEXT NOT NULL UNIQUE, 
        pwd_enc TEXT NOT NULL
    )
"#;

async fn is_exists(tx: &mut SqliteConnection, account: &str) -> Result<bool, SqlxErr> {
    let ret = sqlx::query(
        r#"
        SELECT id FROM tb_user WHERE account = ? LIMIT 1
    "#,
    )
    .bind(account)
    .fetch_optional(tx)
    .await?;

    Ok(ret.is_some())
}

async fn insert_impl(
    tx: &mut SqliteConnection,
    name: &str,
    account: &str,
    pwd_enc: &str,
) -> Result<i64, SqlxErr> {
    let ret = sqlx::query(
        r#"
        INSERT INTO tb_user (name, account, pwd_enc) VALUES (?, ?, ?)
    "#,
    )
    .bind(name)
    .bind(account)
    .bind(pwd_enc)
    .execute(tx)
    .await?;

    Ok(ret.last_insert_rowid())
}

pub async fn init() -> Result<(), SqlxErr> {
    let pool = get_pool();
    pool.execute(CREATE_SQL).await?;

    let mut tx = pool.begin().await?;

    if !is_exists(&mut tx, ROOT_ACCOUNT).await? {
        insert_impl(&mut tx, ROOT_NAME, ROOT_ACCOUNT, &pwd_encrypt(ROOT_PWD)).await?;
    }
    tx.commit().await?;
    Ok(())
}

#[derive(Debug, Serialize)]
pub struct User {
    pub id: i64,
    pub name: String,
    pub account: String,
    pub pwd_enc: String,
}

pub async fn get(account: &str, pwd: &str) -> Result<User, AppErr> {
    let ret = sqlx::query(
        r#"
        SELECT id, name, account, pwd_enc FROM tb_user WHERE account = ?
    "#,
    )
    .bind(account)
    .fetch_optional(get_pool())
    .await?
    .ok_or(new_err_static("没有该用户"))?;

    let user = User {
        id: ret.get(0),
        name: ret.get(1),
        account: ret.get(2),
        pwd_enc: ret.get(3),
    };

    let pwd_enc = pwd_encrypt(pwd);
    if user.pwd_enc != pwd_enc {
        return Err(new_err_static("密码错误"));
    }

    Ok(user)
}

pub async fn create(name: &str, account: &str, pwd: &str) -> Result<i64, AppErr> {
    let pwd_enc = pwd_encrypt(pwd);

    let mut tx = get_pool().begin().await?;

    if is_exists(&mut tx, account).await? {
        return Err(new_err_static("用户名重复"));
    }

    let id = insert_impl(&mut tx, name, account, &pwd_enc).await?;

    tx.commit().await?;

    Ok(id)
}
